<?
class WikiParser {
	
	function WikiParser() {
		$this->image_uri = '';
		$this->toc = array();
		$this->tocnb = 0;
	}
	
	function handle_sections($matches) {
		$level = strlen($matches[1]);
		$content = trim( $matches[2] );
		$this->stop = true;
		
		// build the anchor
		$anchor = preg_replace( "/[^a-zA-Z]/", "", $content );
		$anchor = strtolower( $anchor );

		// add to toc
		$this->toc[ $this->tocnb ] = array( $content, $anchor, $level );
		$this->tocnb++;

		// avoid accidental run-on emphasis
		return $this->emphasize_off() . "<a name=\"$anchor\"></a><h{$level}>{$content}</h{$level}>\n";
	}
	
	function handle_newline($matches) {
		if ($this->suppress_linebreaks) return $this->emphasize_off();
		$this->stop = true;
		// avoid accidental run-on emphasis
		return $this->emphasize_off() . "\n\n<br/><br/>\n\n";
	}
	
	function handle_list($matches,$close=false) {
		$listtypes = array(
			'*'=>'ul',
			'#'=>'ol',
			'o'=>'ul',
		);
		$output = "";
		$newlevel = ($close) ? 0 : strlen($matches[1]);
		while ($this->list_level!=$newlevel) {
			$listchar = substr($matches[1],-1);
			$listtype = $listtypes[$listchar];
			
			//$output .= "[".$this->list_level."->".$newlevel."]";
			
			if ($this->list_level>$newlevel) {
				$listtype = '/'.array_pop($this->list_level_types);
				$this->list_level--;
			} else {
				$this->list_level++;
				array_push($this->list_level_types,$listtype);
			}
			if ( $listchar == 'o' ) $listtype .= " class=\"spread\"";
			$output .= "<{$listtype}>\n";
		}
		if ($close) return $output;
		$output .= "<li>".$matches[2]."</li>\n";
		return $output;
	}
	
	function handle_definitionlist($matches,$close=false) {
		if ($close) {
			$this->deflist = false;
			return "</dl>\n";
		}
		$output = "";
		if (!$this->deflist) $output .= "<dl>\n";
		$this->deflist = true;
		switch($matches[1]) {
			case ';':
				$term = $matches[2];
				$p = strpos($term,' :');
				if ($p!==false) {
					list($term,$definition) = explode(':',$term);
					$output .= "<dt>{$term}</dt><dd>{$definition}</dd>";
				} else {
					$output .= "<dt>{$term}</dt>";
				}
				break;
			case ':':
				$definition = $matches[2];
				$output .= "<dd>{$definition}</dd>\n";
				break;
		}
		return $output;
	}
	
	function handle_preformat($matches,$close=false) {
		if ($close) {
			$this->preformat = false;
			return "</pre>\n";
		}
		$this->stop_all = true;
		$output = "";
		if (!$this->preformat) $output .= "<pre class=\"code\">";
		$this->preformat = true;
		$output .= htmlentities( $matches[1] );
		return $output."\n";
	}

	function handle_blockquote($matches,$close=false) {
		if ($close) {
			$this->blockquote = false;
			return "</blockquote><br clear=\"all\"/>\n";
		}
		$this->stop_all = false; // true;
		$output = "";
		if (!$this->blockquote) $output .= "<blockquote>";
		$this->blockquote = true;
		$output .= $matches[1];
		return $output."<br/>\n";
	}

	function handle_callout($matches,$close=false) {
		if ($close) {
			$this->callout = false;
			return "</div>\n";
		}
		$this->stop_all = false; //true;
		$output = "";
		if (!$this->callout) $output .= "<div class=\"callout\">";
		$this->callout = true;
		$output .= $matches[1];
		return $output."<br/>\n";
	}

	function handle_table($matches,$close=false) {
		if ($close) {
			$this->table = false;
			$this->tableline = 0;
			return "</table>\n";
		}
		$this->stop_all = true;
		if (!$this->table) { $output = "<table><tr class=\"even\">"; $this->tableline = 0; }
		else 
		{
			$output .= "<tr ";
			if ( $this->tableline % 2 == 1 ) $output .= "class=\"odd\"";
			else $output .= "class=\"even\"";
			$output .= ">";
		}
		$this->tableline++;

		$values = preg_split("/[!\|_]/",$matches[2]);

		$index = 0;
		foreach( $values as $value )
		{
			$index++;
			if ( $matches[1] == "|" || ( $matches[1] == "_" && $index == 1 ) ) $output .= "<th>".$value."</th>";
			else $output .= "<td>".$value."</td>";
		}
		$output .= "</tr>\n";
		$this->table = true;
		return $output;
	}

	function handle_horizontalrule($matches) {
		return "<hr />";
	}
	
	function wiki_link($topic) {
		return ucfirst(str_replace(' ','_',$topic));
	}
	
	function handle_image($href,$title,$options) {
		if ($this->ignore_images) return "";
		// if (!$this->image_uri) return $title;
		$href = $this->image_uri . $href;
		$imagetag = sprintf(
			'<img src="%s" alt="%s"/>',
			$href,
			$title
		);
		foreach ($options as $k=>$option) {
			switch($option) {
				case '':
					$imagetag = sprintf(
						'<img src="%s" alt="%s"/>',
						$href,
						$title
					);
					break;
				case 'left':
					$imagetag = sprintf(
						'<img src="%s" alt="%s" align="%s"/>',
						$href,
						$title,
						$option
					);
					break;
				case 'right':
					$imagetag = sprintf(
						'<img src="%s" alt="%s" align="%s"/>',
						$href,
						$title,
						$option
					);
					break;
				case 'center':
					$imagetag = sprintf(
						'<img src="%s" alt="%s" align="%s"/>',
						$href,
						$title,
						$option
					);
					break;
			}
		}
		return $imagetag."\n\n";
	}
	
	function handle_internallink($matches) {
		//var_dump($matches);
		$nolink = false;
		$href = $matches[4];
		$title = $matches[6] ? $matches[6] : $href.$matches[7];
		$namespace = $matches[3];
		if ($namespace=='Image') {
			$options = explode('|',$title);
			$title = array_pop($options);
			return $this->handle_image($href,$title,$options);
		}		
		$title = preg_replace('/\(.*?\)/','',$title);
		$title = preg_replace('/^.*?\:/','',$title);
		if ($this->reference_wiki) {
			$href = $this->reference_wiki.($namespace?$namespace.':':'').$this->wiki_link($href);
		} else {
			$nolink = true;
		}
		if ($nolink) return $title;
		return sprintf(
			'<a href="%s"%s>%s</a>',
			$href,
			($newwindow?' target="_blank"':''),
			$title
		);
	}
	
	function handle_externallink($matches) {
		$href = $matches[2];
		$title = $matches[3];
		if (!$title) {
			$this->linknumber++;
			$title = "[{$this->linknumber}]";
		}
		$newwindow = !preg_match( "/^#.*/", $href );
		return sprintf(
			'<a href="%s"%s>%s</a>',
			$href,
			($newwindow?' target="_blank"':''),
			$title
		);		
	}
	
	function emphasize($amount) {
		$amounts = array(
			2=>array('<em>','</em>'),
			3=>array('<strong>','</strong>'),
			4=>array('<em><strong>','</strong></em>'),
		);
		$output = "";
		// handle cases where emphasized phrases end in an apostrophe, eg: ''somethin'''
		// should read <em>somethin'</em> rather than <em>somethin<strong>
		if ( (!$this->emphasis[$amount]) && ($this->emphasis[$amount-1]) ) {
			$amount--;
			$output = "'";
		}
		$output .= $amounts[$amount][(int) $this->emphasis[$amount]];
		$this->emphasis[$amount] = !$this->emphasis[$amount];
		return $output;
	}
	
	function handle_forcebreak($matches) {
		return "<br/>\n";
	}

	function handle_code($matches) {
		return "<code>".$matches[2]."</code>";
	}

	function handle_coolbanner($matches) {
		return $this->handle_image( "http://www.novell.com/communities/sites/all/themes/novell/images/coolheader.png", "'''{*cool_solutions}'''", array( "center" ) );
	}

	function handle_emphasize($matches) {
		$amount = strlen($matches[1]);
		return $this->emphasize($amount);
	}
	
	function emphasize_off() {
		$output = "";
		if ( $this->emphasis )
			foreach ($this->emphasis as $amount=>$state) {
				if ($state) $output .= $this->emphasize($amount);
			}
		return $output;
	}
	
	function handle_toc($matches) {
		$output = "<h3>Table of Contents</h3>\n";
		ksort( $this->toc );
		foreach( $this->toc as $k=>$v )
		{
			for( $i = 0 ; $i < $v[2] - 1 ; $i++ ) $output .= "&nbsp;&nbsp;&nbsp;&nbsp;";
			$output .= "<a href=\"#{$v[1]}\">".ereg_replace( "'", "", $v[0] )."</a><br/>\n";
		}
		return $output;
	}
	
	function parse_line($line) {
		$line_regexes = array(
			'preformat'=>'^\s(.*?)$',
			'blockquote'=>'^%(.*?)$',
			'callout'=>'^@(.*?)$',
			'table'=>'^([!\|_])(.*?)$',
			'definitionlist'=>'^([\;\:])\s*(.*?)$',
			'newline'=>'^$',
			'list'=>'^([\*\#\o]+) (.*?)$',
			'sections'=>'^(={1,6})(.*?)(={1,6})$',
			'horizontalrule'=>'^----$',
			'coolbanner'=>'^\{\*cool_solutions\}$',
			'toc'=>'^\{\{__TOC__\}\}',
		);
		$char_regexes = array(
			'internallink'=>'('.
				'\[\['. // opening brackets
					'(([^\]]*?)\:)?'. // namespace (if any)
					'([^\]]*?)'. // target
					'(\|([^\]]*?))?'. // title (if any)
				'\]\]'. // closing brackets
				'([a-z]+)?'. // any suffixes
				')',
			'externallink'=>'('.
				'\['.
					'([^\]]*?)'.
					'(\s+[^\]]*?)?'.
				'\]'.
				')',
			'emphasize'=>'(\'{2,4})',
			'code'=>'(\$(.+?)\$)',
			'forcebreak'=>'(\/\/\/)',
		);
		$this->stop = false;
		$this->stop_all = false;
		$called = array();
		//$line = rtrim($line);
		foreach ($line_regexes as $func=>$regex) {
			if (preg_match("/$regex/i",$line,$matches)) {
				$called[$func] = true;
				$func = "handle_".$func;
				$line = $this->$func($matches);
				if ($this->stop || $this->stop_all) break;
			}
		}
		if (!$this->stop_all) {
			$this->stop = false;
			foreach ($char_regexes as $func=>$regex) {
				$line = preg_replace_callback("/$regex/i",array(&$this,"handle_".$func),$line);
				if ($this->stop) break;
			}
		}
		$isline = strlen(trim($line))>0;
		// if this wasn't a list item, and we are in a list, close the list tag(s)
		if (($this->list_level>0) && !$called['list']) $line = $this->handle_list(false,true) . $line;
		if ($this->deflist && !$called['definitionlist']) $line = $this->handle_definitionlist(false,true) . $line;
		if ($this->preformat && !$called['preformat']) $line = $this->handle_preformat(false,true) . $line;
		if ($this->blockquote && !$called['blockquote']) $line = $this->handle_blockquote(false,true) . $line;
		if ($this->callout && !$called['callout']) $line = $this->handle_callout(false,true) . $line;
		if ($this->table && !$called['table']) $line = $this->handle_table(false,true) . $line;
		// suppress linebreaks for the next line if we just displayed one; otherwise re-enable them
		if ($isline) $this->suppress_linebreaks = ($called['newline'] || $called['sections']);
		if ( !$called['preformat'] ) $line = " ".$line;
		return $line;
	}
	
	function parse($text,$title="") {
		$this->nowikis = array();
		$this->list_level_types = array();
		$this->list_level = 0;
		$this->deflist = false;
		$this->linknumber = 0;
		$this->suppress_linebreaks = false;
		$this->page_title = $title;
		$output = "";
		$text = preg_replace_callback('/<nowiki>([\s\S]*)<\/nowiki>/i',array(&$this,"handle_save_nowiki"),$text);
		$lines = explode("\r\n",$text);
		foreach ($lines as $k=>$line) {
			$line = $this->parse_line($line);
			$output .= $line;
		}
		$output = preg_replace_callback('/<nowiki><\/nowiki>/i',array(&$this,"handle_restore_nowiki"),$output);
		return $output;
	}
	
	function handle_save_nowiki($matches) {
		array_push($this->nowikis,$matches[1]);
		return "<nowiki></nowiki>";
	}
	
	function handle_restore_nowiki($matches) {
		return array_pop($this->nowikis);
	}
}
?>
